package org.sampleSpringFramework.aop.aspect;

import lombok.Getter;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.sampleSpringFramework.util.ValidationUtil;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

// 存在多个切面类，能够使得多个切面类按照优先级织入目标方法，
// 同时让切面类的通知按照“先执行全部入度通知，再执行方法，最后逆序执行所有出度通知”的原则织入切入点
public class MultiAspectMethodInterceptor implements MethodInterceptor {
	// 代理的目标类
	private Class<?> targetClass;

	// 排序好的Aspect列表，因为只对目标类的方法进行织入，所以存放的都是与目标类相关的Aspect
	@Getter//测试临时需要
	List<AspectInfo> sortedAspectInfoList;

	/**
	 * 构造函数
	 * @param targetClass 目标类
	 * @param aspectInfoList 切面列表（按照order顺序排列）
	 */
	public MultiAspectMethodInterceptor(Class<?> targetClass,List<AspectInfo> aspectInfoList){
		this.targetClass = targetClass;
		this.sortedAspectInfoList = sortAdvicesInAspects(aspectInfoList);
	}

	/**
	 * 对list进行排序，确保优先级小的Aspect的织入逻辑首先被执行
	 * @param aspectInfoList 维护Aspect
	 * @return 按照order排序后的切面列表
	 */
	private List<AspectInfo> sortAdvicesInAspects(List<AspectInfo> aspectInfoList) {
		Collections.sort(aspectInfoList, (o1,o2) -> {
			return o1.getOrderIndex() - o2.getOrderIndex();
		});
		return aspectInfoList;
	}

	/**
	 * 拦截并执行目标方法，其中需要将通知定序织入到目标方法的切入点（before、after等等）
	 *
	 * @param proxy 代理对象
	 * @param method 委托类方法
	 * @param args 方法参数列表
	 * @param methodProxy 代理方法的MethodProxy对象
	 * @return 方法返回值
	 * @throws Throwable 反射执行方法可能出现的异常
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		Object result;
		// 1、执行所有切面的Before通知
		invokeBeforeAdvices(method,args);
		try {
			// 2、执行目标方法
			result = methodProxy.invokeSuper(proxy, args);//解决cglib空指针异常问题（实际上是粗心，没有初始化ModelAndView的model成员变量）
			// 3、如果方法正常执行，则逆序执行所有切面的afterReturning通知
			result = invokeAfterReturningAdvices(method,args,result);
		} catch (Exception exception) {//执行方法出现异常Exception捕获，但是执行通知出现Throwable则交由上层处理
			// 4、如果方法出现异常，则逆序执行所有切面的afterThrowing通知
			invokeAfterThrowingAdvices(method,args,exception);
			throw new RuntimeException("出现异常，AOP异常通知生效，还是将该异常抛出让程序调用方感知有异常");
		}
		return result;
	}

	/**
	 * 按照Aspect的order顺序执行切面的before方法
	 * @param method 目标方法
	 * @param args 参数列表
	 */
	private void invokeBeforeAdvices(Method method, Object[] args) throws Throwable {
		if(ValidationUtil.isEmpty(sortedAspectInfoList)) return;
		for(AspectInfo aspectInfo: sortedAspectInfoList){
			aspectInfo.getAspectObject().before(targetClass,method,args);
		}
	}

	/**
	 * 按照Aspect的order逆序执行切面的afterReturning方法
	 * @param method 目标方法
	 * @param args 参数列表
	 * @param result 代理对象调用invoke/invokeSuper执行target的目标方法后的返回值
	 * @return 方法调用完时，执行多个切面afterReturning通知之后的返回值
	 */
	private Object invokeAfterReturningAdvices(Method method, Object[] args, Object result) throws Throwable {
		if(ValidationUtil.isEmpty(sortedAspectInfoList)) return result;
		for (int i = sortedAspectInfoList.size() - 1; i >=0; i--) {
			result = sortedAspectInfoList.get(i).getAspectObject().afterReturning(targetClass, method, args, result);
		}
		return result;
	}

	/**
	 * 按照Aspect的order逆序执行切面的afterReturning方法
	 * @param method 目标方法
	 * @param args 参数列表
	 * @param throwable 执行过程中可能出现的异常
	 */
	private void invokeAfterThrowingAdvices(Method method, Object[] args, Throwable throwable) throws Throwable {
		if(ValidationUtil.isEmpty(sortedAspectInfoList)) return;
		for (int i = sortedAspectInfoList.size() - 1; i >=0; i--) {
			sortedAspectInfoList.get(i).getAspectObject().afterThrowing(targetClass, method, args,throwable);
		}
	}
}
